/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.atomojo.tools;

import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 *
 * @author alex
 */
public class DocumentWorker {
   
   static final Logger LOG = Logger.getLogger(DocumentWorker.class.getName());
   static DocumentBuilder docBuilder = null;
   static {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setNamespaceAware(true);
      try {
         docBuilder = factory.newDocumentBuilder();
      } catch (Exception ex) {
         LOG.log(Level.SEVERE,"Cannot configure document builder.",ex);
      }
   }
   
   UserAgent agent;
   Logger logger;
   Transformer serializer;
   
   protected DocumentWorker(Logger log,UserAgent agent)
   {
      this.agent = agent;
      this.logger = log;
      TransformerFactory factory = TransformerFactory.newInstance();
      try {
         this.serializer = factory.newTransformer();
      } catch (Exception ex) {
         LOG.log(Level.SEVERE,"Cannot instantiate serializer.",ex);
      }

   }

   public Logger getLogger() {
      return this.logger;
   }
   
   public void setLogger(Logger log)
   {
      this.logger = log;
   }
      
   public Document getDocument(Reference location)
   {
      Request getCollection = agent.newRequest(Method.GET, location);
      Response getResult = agent.attemptCall(getCollection);
      if (!getResult.getStatus().isSuccess()) {
         if (getResult.getStatus().equals(Status.CLIENT_ERROR_NOT_FOUND)) {
            getLogger().severe("Document "+location+" does not exist.");
         } else {
            getLogger().severe("Cannot get document "+location+", status "+getResult.getStatus());
         }
         return null;
      }
      if (!getResult.isEntityAvailable()) {
         getLogger().severe("Successfuly request on "+location+" but entity is not available.");
         return null;
      }
      Document document = null;
      try {
         Reader r = getResult.getEntity().getReader();
         InputSource source = new InputSource(r);
         source.setSystemId(location.toString());
         document = docBuilder.parse(source);
      } catch (IOException ex) {
         getLogger().severe("I/O error while reading response.");
      } catch (SAXException ex) {
         getLogger().severe("Parse error while reading response: "+ex.getMessage());
      } finally {
         getResult.getEntity().release();
      }
      return document;
   }
   
   public boolean postCreate(Reference location,Document doc) 
   {
      Request createOp = agent.newRequest(Method.POST, location);
      try {
         StringWriter w = new StringWriter();
         writeDocument(doc,w);
         createOp.setEntity(w.toString(), MediaType.APPLICATION_ATOM);
         Response opResult = agent.attemptCall(createOp);
         try {
            return !opResult.getStatus().isSuccess();
         } finally {
            opResult.getEntity().release();
         }
      } catch (IOException ex) {
         ex.printStackTrace();
         return false;
      }
   }
   
   public void writeDocument(Document doc, Writer output)
      throws IOException
   {
      try {
         serializer.transform(new DOMSource(doc),new StreamResult(output));
         output.flush();
      } catch (TransformerException ex) {
         throw new RuntimeException("Serialization exception.",ex);
      }
   }
   
   public Date getLastModified(Reference mediaRef) {
      getLogger().info("Checking modification on "+mediaRef);
      Request mediaHeadRequest = agent.newRequest(Method.HEAD, mediaRef);
      Response mediaHeadResult = agent.attemptCall(mediaHeadRequest);
      if (!mediaHeadResult.getStatus().isSuccess()) {
         if (mediaHeadResult.getStatus().equals(Status.CLIENT_ERROR_NOT_FOUND)) {
            getLogger().warning(mediaRef+" is no longer on the server.");
         } else {
            getLogger().severe("Error checking status of "+mediaRef+", status "+mediaHeadResult.getStatus());
         }
         return null;
      }
      Representation entity = mediaHeadResult.getEntity();
      if (entity==null) {
         getLogger().severe("No Last-Modified header to check against local file for "+mediaRef);
         return null;
      }
      try {
         return entity.getModificationDate();
      } finally {
         entity.release();
      }
      
   }
   
   public Representation getMedia(Reference mediaRef)
   {
      Request getCollection = agent.newRequest(Method.GET, mediaRef);
      Response getResult = agent.attemptCall(getCollection);
      if (!getResult.getStatus().isSuccess()) {
         if (getResult.getStatus().equals(Status.CLIENT_ERROR_NOT_FOUND)) {
            getLogger().severe("Media "+mediaRef+" does not exist.");
         } else {
            getLogger().severe("Cannot get media "+mediaRef+", status "+getResult.getStatus());
         }
         return null;
      }
      if (!getResult.isEntityAvailable()) {
         getLogger().severe("Successfuly request on "+mediaRef+" but entity is not available.");
         return null;
      }
      return getResult.getEntity();
   }
   
   
   
   static public Element findFirst(Element parent,String ns,String localName)
   {
      Node child = parent.getFirstChild();
      while (child!=null) {
         if (child.getNodeType()==Node.ELEMENT_NODE) {
            if (ns.equals(child.getNamespaceURI()) && localName.equals(child.getLocalName())) {
               return (Element)child;
            }
         }  
         child = child.getNextSibling();
      }
      return null;
   }
   
   static public List<Element> getChildrenByName(Element parent,String ns,String localName)
   {
      List<Element> matching = new ArrayList<Element>();
      Node child = parent.getFirstChild();
      while (child!=null) {
         if (child.getNodeType()==Node.ELEMENT_NODE) {
            if (ns.equals(child.getNamespaceURI()) && localName.equals(child.getLocalName())) {
               matching.add((Element)child);
            }
         }  
         child = child.getNextSibling();
      }
      return matching;
   }
}
